#ifndef GLWIDGET_H
#define GLWIDGET_H

#if defined(WIN32) || defined(linux)
    #include <QtOpenGL/qgl.h>
    #include <GL/glu.h>
#elif defined(__APPLE__)
    #include <OpenGL/gl.h>
    #include <OpenGl/glu.h>
#endif

#include <QGLWidget>
#include <vector>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QTimer>
#include <math.h>
#include "common.h"
#include "tinyXML/tinyxml.h"
#include "cellobject.h"
#include "cellmath.h"
#include "intersectionplane.h"
#include "csg.h"

#define pi 3.141592653589793238462643383279

using namespace std;

//!  A QGLWidget class.
/*!
  It is class to handle OpenGL window and function.
*/

class GLWidget : public QGLWidget
{
    Q_OBJECT

public:
    //! A constructor.
    GLWidget(QWidget *parent = 0);

    //! A destructor.
    ~GLWidget();

    //! Create a new scene.
    void newScene();

    //! Save the scene.
    void SaveScene(char *filename);

    //! Set the selected object.
    void setSelectObject(const int o) {selectObject=o;}

    //! Get the selected object index.
    double getSelectObject() {return selectObject;}

    //! Set the seconed selected object.
    void setSecondSelectObject(const int o) {secondSelectObject=o;}

    void setKeyValue(const int k) {keyValue = k;}
    //! Change the mode, call this function when click transform tool group or camera tool group in main window.
    /*!
          \param m can be following values:\n
        CELL_STOP: do nothing.\n
        CELL_CAMERAMOVE: move the camera.\n
        CELL_CAMERAROTATE: rotate the camera.\n
        CELL_CAMERAZOOM: zoom the camera.\n
        CELL_SELECT: select the object in scene.\n
        CELL_TRANSLATE: translate the selected object in scene.\n
        CELL_ROTATE: rotate the selected object in scene.\n
        CELL_SCALE: scale the selected object in scene.\n
    */
    void setMode(const int m) {mode = m;}

    //! Return the mode at the moment.
    int getMode() {return mode;}

    //! Record the mode at the moment, and change mode.
    /*!
          \param m can be following values:\n
        CELL_STOP: do nothing.\n
        CELL_CAMERAMOVE: move the camera.\n
        CELL_CAMERAROTATE: rotate the camera.\n
        CELL_CAMERAZOOM: zoom the camera.\n
        CELL_SELECT: select the object in scene.\n
        CELL_TRANSLATE: translate the selected object in scene.\n
        CELL_ROTATE: rotate the selected object in scene.\n
        CELL_SCALE: scale the selected object in scene.\n
    */
    void setTmpMode(const int m) {tmpMode = mode; mode = m;}

    //! Reset mode to be previous mode.
    void resetTmpMode() {mode = tmpMode;}

    //! Change the view mode, call this function when click view tool group in main window.
    /*!
          \param m can be following values:\n
        CELL_PERSPECTIVE: look at the scene in persective view.\n
        CELL_FOURVIEW: look at the scene in persective view, top view, right view, and front view at the same time.\n
        CELL_TOP: look at the scene from top.\n
        CELL_RIGHT: look at the scene from right.\n
        CELL_FRONT: look at the scene from front.\n
        CELL_PALNEBACED: look at the scene in a 2D view, this view is generated by the intersection of a intersection plane and the 3D mesh.\n
    */
    void setViewMode(const int m) {viewMode = m;}

    //! Change the display mode, call this function when click display tool group in main window.
    /*!
          \param m can be following values:\n
          CELL_TRIANGLE: display the mesh using filled triangles.\n
          CELL_LINE: display the mesh using lines.\n
          CELL_POINT: display the mesh using points.\n
    */
    void setDisplayMode(const int m) {displayMode = m;}

    //! Change the paint mode, call this function when click paint tool group in main window.
    void setPaintMode(const int m) {paintMode = m;}

    //! Change the select mode, call this function when click select tool group in adit mesh widget.
    /*!
          \param m can be following values:\n
          CELL_SELECT: select the object in scene.\n
          CELL_EDITMESHPOINT: select the points in mesh.\n
          CELL_EDITMSEHFACE: select the faces in mesh.\n
    */
    void setSelectMode(const int m) {selectMode = m;}

    //! Reture the select mode at the moment.
    int getSelectMode() {return selectMode;}

    //! Change the feather under mesh edit mode.
    void setEditFeather(const double t) {editFeather = t/100.0;}

    //! Reset Theta, Phi and R of the persective view.
    void reset();

    //! Add a basement membrance from file.
    /*!
          \param num the number of vertice.
          \param vec a std::vector store all vertice.
          \param nor a std::vector store all normals.
    */
    void addGlandular(int num, vector<double> vec, vector<double> nor);

    //! Get the selected object at the moment.
    /*!
          \return the id of selected object.
    */
    int selectedObject() {return selectObject;}

    //! Get the seconed selected object at the moment.
    /*!
          \return the id of seconed selected object.
    */
    int secondSelectedObject() {return secondSelectObject;}

    //! Get the basement membrance.
    /*!
          \return the point of basement membrance.
    */
    CellObject* getGlandular() {return glandular;}

    //! Set the object at indexth position.
    void setObject(CellObject *_object, int index) {objects[index] = _object;}

    //! Set the Glandular.
    void setGlandular(CellObject *_glandular) {glandular = _glandular;}

    //! When press the delete button in edit mesh widget, call this function to delete select points or faces
    void deleteSelect();

    //! Add a sphere to the scene.
    CellObject *addSphere();

    //! Add a cylinder to the scene.
    CellObject *addCylinder();

    //! Add a open cylinder to the scene.
    CellObject *addOpenCylinder();

    //! Add a cube to the scene.
    CellObject *addCube();

    //! Add a plane to the scene.
    CellObject *addPlane();

    //! Add a cone to the scene.
    CellObject *addCone();

    //! Add a line to the scene.
    CellObject *addLine();

    //! Add a object at the last position of objects vector.
    void addObject(CellObject *obj);

    //! Add a object at the indexth position of objects vector.
    void addObject(CellObject *obj, int index);

    //! Intersection plane.
    IntersectionPlane *plane;

    //! Transform a CellObject to CSG.
    void ObjectToCSG(CellObject *a, CSG *b);

    //! Transform a CSG to CellObject.
    void CSGToObject(CSG *a, CellObject *b);

    //! Union operation.
    void Union(CellObject *a, CellObject *b, CellObject *result);

    //! Intersect operation.
    void Intersect(CellObject *a, CellObject *b, CellObject *result);

    //! Subtract operation.
    void Subtract(CellObject *a, CellObject *b, CellObject *result);

    //! Union operation.
    void Union();

    //! Intersect operation.
    void Intersect();

    //! Subtract operation.
    void Subtract();

    void test();

    //! Delete the selected object.
    void deleteSelectedObject();

    //! Delete the object at the indexth position.
    void deleteObject(int index);

    //! Generates a mesh from a line
    void generateMesh();

    //! Set the copied object id.
    void setCopy(int m) {copyObject = m;}

    //! Paste the copied object.
    void pasteObject();

    //! Return the objects vector.
    vector<CellObject*> getObjects() {return objects;}

    //! Return the number of spheres.
    int getSphereNum() {return sphereNum;}

    //! Return the number of cylinders.
    int getCylinderNum() {return cylinderNum;}

    //! Return the number of cubes.
    int getCubeNum() {return cubeNum;}

    //! Mouse Press Event, call this function in mainwindow.
    void myMousePressEvent(QMouseEvent *event);

    //! Mouse Release Event, call this function in mainwindow.
    void myMouseReleaseEvent(QMouseEvent *event);

    //! Mouse Move Event, call this function in mainwindow.
    void myMouseMoveEvent(QMouseEvent *event);

    //! Print list of objects' names
    void printObjects();

    //! Returns false if there are objects in the scene, true otherwise
    bool emptyScene();

public slots:
    void setTheta(double angle);
    void setPhi(double angle);
    void setR(double angle);
    void setTranslateX(double length);
    void setTranslateY(double length);
    void setTranslateZ(double length);
    void addTranslateX(double length);
    void addTranslateY(double length);
    void addTranslateZ(double length);
    void addTranslateCtrlPt1X(double length);
    void addTranslateCtrlPt1Y(double length);
    void addTranslateCtrlPt1Z(double length);
    void addTranslateCtrlPt2X(double length);
    void addTranslateCtrlPt2Y(double length);
    void addTranslateCtrlPt2Z(double length);
    void setRotateX(double angle);
    void setRotateY(double angle);
    void setRotateZ(double angle);
    void addRotateX(double angle);
    void addRotateY(double angle);
    void addRotateZ(double angle);
    void setScaleX(double s);
    void setScaleY(double s);
    void setScaleZ(double s);
    void setTranslateX(QString l);
    void setTranslateY(QString l);
    void setTranslateZ(QString l);
    void setRotateX(QString l);
    void setRotateY(QString l);
    void setRotateZ(QString l);
    void setScaleX(QString l);
    void setScaleY(QString l);
    void setScaleZ(QString l);
    void setEditFeather(QString l);
    void setInputType();

    void setSphereRadius(QString l);
    void setSphereHeightSeg(QString l);
    void setSphereAxisSeg(QString l);

    void setCylinderRadius(QString l);
    void setCylinderHeight(QString l);
    void setCylinderHeightSeg(QString l);
    void setCylinderAxisSeg(QString l);

    void setPlaneWidth(QString l);
    void setPlaneLength(QString l);
    void setPlaneSeg(QString l);

    void setCubeWidth(QString l);
    void setCubeHeight(QString l);
    void setCubeDepth(QString l);
    void setCubeWidthSeg(QString l);
    void setCubeHeightSeg(QString l);
    void setCubeDepthSeg(QString l);

signals:
    void xTranslationChanged(QString length);
    void yTranslationChanged(QString length);
    void zTranslationChanged(QString length);
    void xRotationChanged(QString angle);
    void yRotationChanged(QString angle);
    void zRotationChanged(QString angle);
    void xScalationChanged(QString s);
    void yScalationChanged(QString s);
    void zScalationChanged(QString s);
    void inputTypeChanged(int s);
    void rightClickSelect(QPoint);

protected:
    void initializeGL();
    void paintGL();
    void resizeGL(int width, int height);
    void wheelEvent(QWheelEvent *event);


private:
    //! In pick function, may call this function to process hits in the OpenGL window. It base on the OpenGL function pick-select.
    /*!
          \param hits the number of hit objects.
          \param buffer the Z-buffer information of all the hit objects.
          \param flag can be following values:\n
          0: select object in scene.\n
          1: select translate axis.\n
          2: select rotate axis.\n
          3: select scale axis.\n
          4: select one point under edit mesh mode.\n
          5: select one face under eidt mesh mode.\n
    */
    void processHits(GLint hits, GLuint buffer[], int flag);

    //! Under the select mode and perspectvie, top, front, right view, when click the left mouse button, call this function to return the select object.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
    */
    void pick(int x, int y);

    //! Under the select mode and 4 view, when click the left mouse button, call this function to return the select object.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
    */
    void pickInFourView(int x, int y);

    //! Under the translate mode, when click the left mouse button, call this function to return the select translate axis.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
    */
    void pickTranslateAxis(int x, int y);

    //! Under the rotate mode, when click the left mouse button, call this function to return the select rotate axis.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
    */
    void pickRotateAxis(int x, int y);

    //! Under the scale mode, when click the left mouse button, call this function to return the select scale axis.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
    */
    void pickScaleAxis(int x, int y);

    //! Under the edit mesh base on points selectmode, when click the left mouse button, call this function to return the select point.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
    */
    void pickEditPoint(int x, int y);

    //! Under the edit mesh base on faces selectmode, when click the left mouse button, call this function to return the select face.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
    */
    void pickEditFace(int x, int y);

    //! Under the edit mesh base on points selectmode, when click the left mouse button and drag, call this function to return all the points in the drag area.
    /*!
          \param ax mouse click position x.
          \param ay mouse click position y.
          \param bx mouse drag position x.
          \param by mouse drag position y.
    */
    void pickPointsInArea(int ax, int ay, int bx, int by);

    //! Under the edit mesh base on faces selectmode, when click the left mouse button and drag, call this function to return all the faces in the drag area.
    /*!
          \param ax mouse click position x.
          \param ay mouse click position y.
          \param bx mouse drag position x.
          \param by mouse drag position y.
    */
    void pickFacesInArea(int ax, int ay, int bx, int by);

    //! Under the tanslate mode, call this function to draw tanslate axis at p(x, y, z).
    /*!
          \param tx tanslation axis initial translation value x.
          \param ty tanslation axis initial translation value y.
          \param tz tanslation axis initial translation value z.
          \param rx tanslation axis initial rotation value x.
          \param ry tanslation axis initial rotation value y.
          \param rz tanslation axis initial rotation value z.
    */
    void drawEditMeshTranslateAxis(double x, double y, double z, double rx, double ry, double rz);

    //! Under the translate mode, when click the left mouse button, call this function to return the select tanslation axis.
    /*!
          \param x mouse click position x.
          \param y mouse click position y.
          \param tx tanslation axis initial translation value x.
          \param ty tanslation axis initial translation value y.
          \param tz tanslation axis initial translation value z.
          \param rx tanslation axis initial rotation value x.
          \param ry tanslation axis initial rotation value y.
          \param rz tanslation axis initial rotation value z.
    */
    void pickEditMeshTranslateAxis(int x, int y, double tx, double ty, double tz, double rx, double ry, double rz);

    //! Draw the 3D axis at the left-down corner.
    void drawAxis();

    //! Draw the 3D wire plane.
    void drawWire();

    //! Draw the 2D gradient back ground.
    void drawBackground();

    //! Draw all of the objects in scene.
    void drawGlandular();

    //! Draw the selected area when drag the mouse.
    /*!
          \param ax mouse click position x.
          \param ay mouse click position y.
          \param bx mouse drag position x.
          \param by mouse drag position y.
    */
    void drawSelectArea(int ax, int ay, int bx, int by);

    //! Under the translate mode, draw the tranlate axis.
    /*!
          \param x tanslation axis initial translation value x.
          \param y tanslation axis initial translation value y.
          \param z tanslation axis initial translation value z.
          \param objectId selected object in the moment.
    */
    void drawTranslateAxis(double x, double y, double z, int objectId);

    //! Under the translate mode, draw the tranlate plane.
    /*!
          \param x tanslation axis initial translation value x.
          \param y tanslation axis initial translation value y.
          \param z tanslation axis initial translation value z.
          \param objectId selected object in the moment.
    */
    void drawTranslatePlane(double x, double y, double z, int objectId);

    //! Under the translate mode, draw the tranlate point.
    /*!
          \param x tanslation axis initial translation value x.
          \param y tanslation axis initial translation value y.
          \param z tanslation axis initial translation value z.
          \param objectId selected object in the moment.
    */
    void drawTranslatePoint(int objectId);


    //! Under the rotate mode, draw the rotate axis.
    /*!
          \param x tanslation axis initial translation value x.
          \param y tanslation axis initial translation value y.
          \param z tanslation axis initial translation value z.
          \param objectId selected object in the moment.
    */
    void drawRotateAxis(double x, double y, double z, int objectId);

    //! Under the scale mode, draw the scale axis.
    /*!
          \param x tanslation axis initial translation value x.
          \param y tanslation axis initial translation value y.
          \param z tanslation axis initial translation value z.
          \param objectId selected object in the moment.
    */
    void drawScaleAxis(double x, double y, double z, int objectId);

    //! Draw a cube
    /*!
          \param length the length of the cube.
    */
    void drawSolidCube(double length);

    //! Draw a rectangle
    /*!
          \param width the width of the rectangle.
          \param height the height of the rectangle.
          \param depth the depth of the rectangle.
    */
    void drawSolidRectangle(double width, double height, double depth);

    //! Draw a cone
    /*!
          \param height the height of the cone.
          \param radius the radius of the undersurface.
    */
    void drawSolidCone(double height, double radius);

    //! The camera angel at latitude under perspective view mode..
    double Theta;

    //! The camera angel at longitude under perspective view mode..
    double Phi;

    //! The distance from camera to (0, 0, 0) under perspective view mode.
    double R;

    //! The distance from camera to (0, 0, 0) under top view mode.
    double topR;

    //! The distance from camera to (0, 0, 0) under front view mode.
    double frontR;

    //! The distance from camera to (0, 0, 0) under right view mode.
    double rightR;

    //! The distance from camera to (0, 0, 0) under plane-based view mode.
    double planeR;

    double offsetX, topOffsetX, frontOffsetX, rightOffsetX, planeOffsetX;
    double offsetY, topOffsetY, frontOffsetY, rightOffsetY, planeOffsetY;

    //! The key value.
    int keyValue;

    //! Determine the mouse interaction.
    /*!
        mode can be following values:\n
        CELL_STOP: do nothing.\n
        CELL_CAMERAMOVE: move the camera.\n
        CELL_CAMERAROTATE: rotate the camera.\n
        CELL_CAMERAZOOM: zoom the camera.\n
        CELL_SELECT: select the object in scene.\n
        CELL_TRANSLATE: translate the selected object in scene.\n
        CELL_ROTATE: rotate the selected object in scene.\n
        CELL_SCALE: scale the selected object in scene.\n
    */
    int mode;

    //! Record the previous mode.
    /*!
        tmpMode can be following values:\n
        CELL_STOP: do nothing.\n
        CELL_CAMERAMOVE: move the camera.\n
        CELL_CAMERAROTATE: rotate the camera.\n
        CELL_CAMERAZOOM: zoom the camera.\n
        CELL_SELECT: select the object in scene.\n
        CELL_TRANSLATE: translate the selected object in scene.\n
        CELL_ROTATE: rotate the selected object in scene.\n
        CELL_SCALE: scale the selected object in scene.\n
    */
    int tmpMode;

    //! Determine the view mode.
    /*!
        viewMode can be following values:\n
        CELL_PERSPECTIVE: look at the scene in persective view.\n
        CELL_FOURVIEW: look at the scene in persective view, top view, right view, and front view at the same time.\n
        CELL_TOP: look at the scene from top.\n
        CELL_RIGHT: look at the scene from right.\n
        CELL_FRONT: look at the scene from front.\n
        CELL_PALNEBACED: look at the scene in a 2D view, this view is generated by the intersection of a intersection plane and the 3D mesh.\n
    */
    int viewMode;

    //! Determine the display mode.
    /*!
          displayMode can be following values:\n
          CELL_TRIANGLE: display the mesh using filled triangles.\n
          CELL_LINE: display the mesh using lines.\n
          CELL_POINT: display the mesh using points.\n
    */
    int displayMode;

    //! Determine the paint mode.
    int paintMode;

    //! Determine the select mode.
    /*!
          selectMode can be following values:\n
          CELL_SELECT: select the object in scene.\n
          CELL_EDITMESHPOINT: select the points in mesh.\n
          CELL_EDITMSEHFACE: select the faces in mesh.\n
    */
    int selectMode;

    //! width of the OpenGL window.
    int windowWidth;

    //! height of the OpenGL window.
    int windowHeight;

    //! The index of the selected object at this moment.
    int selectObject;

    //! The index of the second selected at this moment.
    int secondSelectObject;

    //! Selected translate axis at this moment under translate mode.
    int selectTranslateAxis;

    //! Selected rotate axis at this moment under rotate mode.
    int selectRotateAxis;

    //! Selected scale axis at this moment under scale mode.
    int selectScaleAxis;

    //! Basement membrance object.
    CellObject *glandular;

    //! Store all the user constructed objects.
    vector<CellObject*> objects;

    //! Mouse click position at last time.
    QPoint lastClickPos;

    //! Mouse position at last time.
    QPoint lastPos;

    //! Mouse click position at this time.
    QPoint thisPos;

    //! Determine if draw the selected area.
    bool isAreaSelected;

    //! Determine if edit mesh with fearth.
    double editFeather;

    //! The id of the copy object.
    int copyObject;

    //! The cut object.
    CellObject *cutObject;

    //! Cross section plane
    CellObject *crossPlane;

    //! Record the number of spheres.
    int sphereNum;

    //! Record the number of cylinders.
    int cylinderNum;

    //! Record the number of cubes.
    int cubeNum;

    //! Record the number of meshes.
    int meshNum;
};

#endif // GLWIDGET_H
